local base = _G
local table = base.table
local select = base.select
local math = base.math
module("gui")

ListG = {}
ListGMT = { __index = ListG }

base.setmetatable(ListG, { __index = ContainerG })

ListG.create = function(x, y, width, rows)
  local styles = get_styles('padding', 'font', 'line_spacing')
  local colors = get_colors('background', 'border', 'font', 'font_highlight', 'highlight')
  local line = styles.line_spacing
  local height = rows * line + styles.padding * 2

  local self = Container(x, y, width, height, ListGMT)

  self.styles = styles
  self.colors = colors

  self.items = {}
  self.index = 0
  self.hindex = 0
  self.offset = 0
  self.rows = rows
  self.old_index = 0
  self.old_items = #self.items
  
  self.scrollbar = Scrollbar(width - 12, 0, 12, self.height, 1, false)
  self.scrollbar.node.depth = -1
  self:add_child(self.scrollbar)
  
  self.scrollbar.slider.on_change = function(scrollbar, value)
    self.offset = value - 1
  end

  return self
end

ListG.destroy = function(self)
  self.styles = nil
  self.colors = nil
  
  self.items = nil
  self.index = nil
  self.old_index = nil
  self.old_items = nil
  self.hindex = nil
  self.offset = nil
  self.rows = nil
  self.scrollbar = nil
  ContainerG.destroy(self)
end

ListG.update_offset = function(self)
  if self.index == 0 or self.index <= self.offset or self.index >= self.offset + self.rows then
    self.offset = math.min(self.offset, #self.items - self.rows, self.index - 1)
    self.offset = math.max(self.offset, self.index - self.rows, 0)
  end
end

ListG.set_index = function(self, i)
  if i > 0 then
    i = math.max(i, 1)
    i = math.min(i, #self.items)
  end
  if i ~= self.index then
    self.old_index = self.index
    self.index = i
    self:update_offset()
    self:on_change(i)
  end
end

-- convert position in pixels to item index
ListG.get_index = function(self, x, y)
  if #self.items == 0 then
    return 0
  end
  local line = self.styles.line_spacing
  local s = math.ceil(y/line)
  s = math.max(s, 1)
  s = math.min(s, #self.items, self.rows)
  return s + self.offset
end

-- handle mouse input
ListG.mouse_press = function(self, button, x, y)
  ContainerG.mouse_press(self, button, x, y)
  if self.focus ~= nil then
    return
  end
  self.hindex = 0
  local i = self:get_index(x, y)
  self:set_index(i)
end

ListG.double_click = function(self, button, x, y)
  local nx, ny = self.scrollbar:get_position()
  if x > nx then
    ContainerG.double_click(self, button, x, y)
    return
  end
  self:on_select(self.index)
end

-- update hover index
ListG.mouse_move = function(self, x, y, x2, y2)
  if self:test_point(x, y) == false then
    return
  end
  local nx, ny = self.scrollbar:get_position()
  if x > nx then
    ContainerG.mouse_move(self, x, y, x2, y2)
    return
  end
  self.hindex = self:get_index(x, y)
end

-- scroll using the mouse wheel
ListG.wheel_move = function(self, z)
  self.scrollbar:wheel_move(z)
end

-- update selection index
ListG.dragging = function(self, button, x, y)
  if self.focus then
    ContainerG.dragging(self, button, x, y)
    return
  end
  local i = self:get_index(x, y)
	self:set_index(i)
end

-- handle key commands
ListG.key_command = function(self, key, ctrl, shift)
  if #self.items == 0 then
    return 0
  end
  if key == base.KEY_LEFT or key == base.KEY_UP or key == base.KEY_RIGHT or key == base.KEY_DOWN then
    -- move to previous or next element
    local dir = 1
    if key == base.KEY_LEFT or key == base.KEY_UP then
      dir = -dir
    end
    if self.index + dir > 0 then
      self:set_index(self.index + dir)
    end
  elseif key == base.KEY_HOME or key == base.KEY_PGUP or key == base.KEY_END or key == base.KEY_PGDOWN then
    -- jump to first or last element
    local i = 1
    if key == base.KEY_END or key == base.KEY_PGDOWN then
      i = #self.items
    end
    self:set_index(i)
  elseif key == base.KEY_ENTER then
    self:on_select(self.index)
  end
  -- callback has destroyed this object
  if self.node == nil then
    return
  end
  local s = math.max(#self.items - self.rows + 1, 1)
  self.scrollbar.slider.segments = s
  self.scrollbar.slider.value = self.offset + 1
end

-- adds new items to the list
ListG.add_items = function(self, ...)
  for i = 1, select('#', ...) do
    local arg = select(i, ...)
    table.insert(self.items, arg)
  end
end

ListG.update_dimensions = function(self)
  -- update if the user has changed the index or items
  if self.index ~= self.old_index or #self.items ~= self.old_items then
    self.index = math.max(self.index, 0)
    self.index = math.min(self.index, #self.items)
    self.old_items = #self.items
    self.old_index = self.index
  end

  local s = math.max(#self.items - self.rows + 1, 1)
  self.scrollbar.slider.segments = s
  self.scrollbar:set_position(self.width - 12, 0)
  self.scrollbar:set_size(12, self.height)
end

ListG.redraw = function(self, dt)
  self.scrollbar:redraw(dt)

  local c = self.sprite.canvas
  c:clear()

  local alpha = 0.5
  if self.is_focused == true then
    alpha = 1
  elseif self.is_hovered == true then
    alpha = 0.75
  end
  
  -- draw background rectangle
  local l, t, r, b = 0, -self.height, self.width, 0
  c:rect(l, t, r, b)
  c:set_fill_style(self.colors.background, 1)
  c:fill()
  c:rect(l - 1, t - 1, r, b + 1)
  c:set_line_style(1, self.colors.border, alpha)
  c:stroke()

  -- todo: simplify
  -- draw the list items
  local w = self.width - 12
  l, t, r, b = 0, 0, w, self.height
  local pad = self.styles.padding
  local y1 = pad - self.height - pad * 2
  local y2 = y1 + self.height
  local x1 = pad
  local x2 = w - pad
  local line = self.styles.line_spacing
  local bs =(line - self.styles.font:get_size())/2
  local si = 1 + self.offset
  local ei = math.min(#self.items, si + self.rows - 1)
  for i = si, ei do
    -- write the text
    local out = self.items[i]
    out = self.styles.font:clamp(out, x2 - x1, "...")
    c:move_to(x1 + 1, y2 - line*(i - self.offset) + bs)
    c:set_font(self.styles.font, self.colors.font, alpha)
    c:write(out)
  end

  -- draw the selected item
  if self.index > 0 and #self.items > 0 then
    if self.index > self.offset and self.index <= self.offset + self.rows then
      local oi = self.index - self.offset
      c:rect(x1, y2 - line * oi, x2, y2 - line *(oi - 1))
      c:set_fill_style(self.colors.highlight, alpha)
      c:fill_preserve()
      c:set_line_style(1, self.colors.font_highlight, alpha)
      c:stroke()
      c:move_to(x1 + 1, y2 - line * oi + bs)
      c:set_font(self.styles.font, self.colors.font_highlight, 1)
      local out = self.items[self.index]
      out = self.styles.font:clamp(out, x2 - x1, "...")
      c:write(out)
    end
  end 

  -- draw the hovered item
  if self.hindex ~= self.index and self.is_hovered == true then
    if self.hindex > self.offset and self.hindex <= self.offset + self.rows then
      local i = self.hindex - self.offset
      c:rect(x1, y2 - line * i, x2, y2 - line *(i - 1))
      c:set_fill_style(self.colors.highlight, alpha/2)
      c:fill_preserve()
      c:set_line_style(1, self.colors.font_highlight, alpha/2)
      c:stroke()
    end
  end
end

ListG.update = function(self, dt)
  ContainerG.update(self, dt)
  self:update_dimensions()
  self:redraw(dt)
end

ListG.on_change = function(self, index)

end

ListG.on_select = function(self, index)

end

List = ListG.create